PS 2011 - Laboratorio OS161
OS/161 contiene un insieme di programmi utente già predisposti per l'esecuzione sul kernel. Questo insieme include programmi di utilità comuni in ambito UNIX, quali ls and cat, oltre a vari altri programmi di test. I sorgenti di tali programmi si trovano, a partire dalla radice dei sorgenti di kernel, in user/{bin,sbin,testbin}.
Molti dei programmi sopra citati non sono in grado di funzionare correttemente, con OS161 nella forma iniziale, in quanto molte “system calls” non sono ancora implementate. Per farli funzionare correttamente occorre quindi implementare tali system calls.
Per creare gli eseguibili dei programmi utente occorre, a partire dai direttori dei file sorgenti, eseguire
bmake
Gli eseguibili creati, in formato ELF, sono installati nel direttorio pso-os161/root/ nei sotto-direttori bin, sbin e testbin, rispettivamente. Un insieme di programmi eseguibili gà predisposti è presente in tali direttori.
Obiettivo del laboratorio n. 5 è analizzare la fase di “load” di un eseguibile, sino all'avvio dell'esecuzione, NONOSTANTE il programma non funzioni poi correttamente (nel successivo laboratorio si realizzaranno due system calls in grado di supportare INPUT e OUTPUT, permettendo quindi l'esecuzione completa di alcuni programmi di test).
Per analizzare la fase di load e avvio dell'esecuzione di un programma di test, occorre attivare il programma dal menu di os161, mediante il comando “p programma”, ad esempio “p testbin/palin”, “p sbin/reboot”, dopo aver impostato uno o più breakpoints del debugger, sulle funzioni che eseguono tali operazioni. Si consiglia, ad esempio, di mettere breakpoints su:
common_prog, thread_fork, thread_startup, cmd_progthread, runprogram, load_elf, ...
Si analizzi, in particolare, la fase di attivazione di un nuovo thread e di caricamento del file ELF. Si determini a quali indirizzi di memoria (e come) vengono caricate le parti del file ELF. A tale scopo, si noti che in varie parti del codice sorgente estbdi kernel, compaiono istruzioni del tipo DEBUG(..., che eseguono eventuali kprintf, controllate dal valore della variabile globale dbflags. Per la definizione completa dei valori da assegnare a dbflags, si veda il file kern/include/lib.h. Se ad esempio, si volessero attivare le DEBUG in load_segment, occorrerebbe attivare, in dbflags, il flag DB_EXEC, definito come 0x040. A tale scopo, si può ad esempio usare l'istruzione di gdb: set dbflags = 0x040.
Utilizzando sia le istruzioni DEBUG che GDB, si chiede pertanto di esaminare l'esecuzione di load_elf, di determinare come viene predisposto l' spazio delgli indirizzi del thread in esecuzione (segmento di codice, dati, e stack) e come viene attivata l'esecuzione del programma in user-mode.
Si provi inoltre
a verificare quale/quanta memoria viene allocata per il thread in
esecuzione, e quale di questa memoria viene rilasciata al termine di
tale esecuzione. Quale funzione occorrerebbe modificare per
rilasciare tutta la memoria utilizzata ? Sarebbe possibile una
minimas modifica al kernel tale da ripristinare lo stato della
memoria precedente all'esecuzione del thread ?
La funzione principale di un kernel è quella di fornire il supporto per programmi user-level. Tale supporto è in genere fornito tramite le "system calls". Si noti che un programma utente viene attivato dal menu di os161 mediante il comando “p programma”, ad esempio “p testbin/palin”, “p sbin/reboot”.
Os161, nella versione base, fornisce solo 2 system call (reboot e __time). Ad esempio, la system call reboot(), viene implementata dalla funzione di kernel sys_reboot(), in main.c. In GDB, se si vuole mettere un breakpoint su sys_reboot e attivarla, basta chiamare il programma sbin/reboot (comando “p sbin/reboot”). Una volta che gdb si trova in sys_reboot(), usare backtrace, oppure up/down, per localizzare la sequanza delle chiamate. In breve, la funzione utente reboot(), attiverà indirettamente una trap di sistema, che chiamerà la funzione syscall() (file kern/arch/mips/syscall.c), la quale, mediante un costrutto switch/case, attiverà la sys_reboot().
A questo punto è possible tentare di creare una nuova system call. Per generare una nuova system call occorre:
· creare nuove funzioni utente, oppure utilizzarne di esistenti (ma ancora prive di supporto, in os161 base). Nel caso specifico, si consiglia di utilizzare le funzioni write e read, che vengono indirettamente richiamata da printf e scanf.
· Generare nuove system call da associare alle funzione utente (una versione molto semplificata della sys_write() è già presente nel kit OS161 utilizzato in laboratorio).
o Occorre un codice intero (si veda il file kern/include/kern/syscall.h), da utilizzare nel costrutto switch/case (in syscall()) visto in precedenza (le costanti SYS_read e SYSsys_write sono già definite (50 e 55) e quindi utilizzabili).
o Occorre una funzione da richiamare, nel costrutto switch. Utilizzando le convenzioni già utilizzate, si consiglia di generare (in analogia a sys_reboot) le funzioni sys_write(). La funzione sys_reboot() si trova nel file menu.c, ma si consiglia di generare un nuovo sorgente nel direttorio kern/syscall (ad es. stdio_syscalls.c, analogo a time_syscalls.c), nel quale realizzare la funzione sys_write()).
o Passaggio dei parametri. La funzione syscall(), attivata da trap, riceve come parametro struct trapframe *tf. La struct puntata contiene, tra le altre informazioni, i parametri passati alla write (un puntatore a una stringa e il numero di caratteri da stampare – ATTENZIONE, la stringa non è necessariamente terminata da ‘\0’). Tali parametri sono reperibili in tf->tf_a0, …_a1, …_a2, …_a3. Si vedano le system call già implementate, lavorando in modo analogo per sys_write().
· Aggiungere le informazioni necessarie per fare make di un nuovo kernel, e realizzarlo:
o Aggiungere il prototipo della sys_write al file kern/include/syscall.h.
o Determinare a quale ASSTx si fa riferimento in kern/conf (ASST0 è OK, se si usa ASST2, attenzione a disabilitare (commentare) options synchprobs in ASST2).
o In conf.kern aggiungere il nuovo file (es. syscalls/stdio_syscalls.c)
o Rifare il make del kernel
· Eseguire (possibilmente in debug) un programma utente che attivi la system call. Un esempio è la testbin/palin (sorgenti in user/testbin). Attenzione, i programmi attivabili da menu come “p testbin/xxx” non supportano gli argomenti al main (per abilitarli serve una implementazione della malloc, in quanto le stringhe vanno passate dal menu ai threas che mandano in esecuzione (in user space) un file eseguibile (elf). Nella versione attuale, quindi, i programmi vanno chiamati senza argomenti (quindi non funzionano completamente, o vanno modificati).
Informazioni più dettagliate sulle system calls sono reperibili sul documento “understanding system calls”
La funzione utente _exit, che va chiamata al termine dell'esecuzione di un programma utente. A tale funzione corrisponde la system call SYS__exit (attenzione: con 2 '_') tale system call dovrebbe chiudere il thread che ha attivato il programma utente. Provare ad esaminare come terminano i thread di sistema (tt1, tt2, tt3). Esaminare in particolare, utilizzando il debugger, le funzioni thread_startup, cmd_progthread, runprogram.